/**
 * 
 */
package net.entelijan.cobean.bind.impl;

import java.beans.PropertyDescriptor;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Collection;

import net.entelijan.cobean.bind.IBeanProperty;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.BeanWrapper;
import org.springframework.beans.BeanWrapperImpl;
import org.springframework.beans.BeansException;

class PropertyBeanPropertyVariante implements IBeanProperty {

	private static Log log = LogFactory
			.getLog(PropertyBeanPropertyVariante.class);

	private BeanWrapper beanWrapper;

	private String property;

	private Class<?> primitive;

	public PropertyBeanPropertyVariante(Object bean, String property) {
		super();
		this.beanWrapper = new BeanWrapperImpl(bean);
		this.property = property;
		Class<?> type;
		try {
			PropertyDescriptor pd = this.beanWrapper
					.getPropertyDescriptor(this.property);
			if (pd == null) {
				throw new IllegalStateException(
						"Could not get a descriptor for the property '"
								+ this.property + "'");
			}
			type = pd.getPropertyType();
		} catch (BeansException e) {
			type = null;
			log.warn("Could not retreive class of property '" + property + "'");
		}
		if (type != null && type.isPrimitive()) {
			this.primitive = type;
		}

	}

	public Object getValue() {
		Object re = null;
		if (this.beanWrapper.isReadableProperty(this.property)) {
			re = this.beanWrapper.getPropertyValue(this.property);
		}
		return re;
	}

	public void setValue(Object obj) {
		if (this.primitive != null && obj == null) {
			if (this.primitive == java.lang.Integer.TYPE) {
				obj = Integer.valueOf(0);
			} else if (this.primitive == java.lang.Long.TYPE) {
				obj = Long.valueOf(0L);
			} else if (this.primitive == java.lang.Double.TYPE) {
				obj = Double.valueOf(0.0);
			} else if (this.primitive == java.lang.Boolean.TYPE) {
				obj = Boolean.valueOf(false);
			} else {
				// TODO Complete the list of primitives
				throw new IllegalStateException("Unknown primitive '"
						+ this.primitive + "'");
			}
		}
		if (this.beanWrapper.isWritableProperty(this.property)) {
			this.beanWrapper.setPropertyValue(this.property, obj);
		}
	}

	public Object createPlaceholder() {
		try {
			Class<?> wrappedClass = this.beanWrapper.getWrappedClass();
			if (CobeanSpringUtil.isCglibProxyFromClass(wrappedClass)) {
				wrappedClass = CobeanSpringUtils
						.getOriginalClassFromCglib(wrappedClass);
				BeanWrapper bw = new BeanWrapperImpl(wrappedClass);
				PropertyDescriptor propDesc = bw
						.getPropertyDescriptor(this.property);
				Class<?> type = propDesc.getPropertyType();
				if (type.isInterface()) {
					return createDynProxy(type);
				} else {
					return type.newInstance();
				}
			} else if (CobeanSpringUtil.isDynamicProxyFromClass(wrappedClass)) {
				return CobeanSpringUtils
						.getPlaceholderFromDynamicProxy(wrappedClass);
			} else {
				throw new IllegalStateException("'" + wrappedClass
						+ "' is neither cglib nor dynamic proxy.");
			}
		} catch (Exception e) {
			throw new IllegalStateException("Could not create placeholder. "
					+ e.getMessage(), e);
		}
	}

	private Object createDynProxy(Class<?> type) {
		return Proxy.newProxyInstance(getClass().getClassLoader(),
				new Class[] { type }, new InvocationHandler() {

					@Override
					public Object invoke(Object proxy, Method method,
							Object[] args) throws Throwable {
						log.info("[invoke] " + method.getName());
						return null;
					}
				});
	}

	@Override
	public String toString() {
		return "BeanProperty('" + this.property + "' of "
				+ this.beanWrapper.getWrappedInstance() + "'";
	}

	@Override
	public String getPropertyName() {
		return this.property;
	}

	@Override
	public boolean isCollection() {
		return this.beanWrapper.getWrappedInstance() instanceof Collection<?>;
	}

	@Override
	public Object getBean() {
		return this.beanWrapper.getWrappedInstance();
	}

}